from pathlib import Path
from urllib.parse import urljoin

from geoalchemy2 import Geometry, WKBElement, WKTElement
from passlib.context import LazyCryptContext
from sqlalchemy import Integer, LargeBinary, JSON, String, TypeDecorator
from sqlalchemy.ext.mutable import MutableDict, MutableList


# ----------------------------------------------------------
#
#                           扩展类型
#
# ----------------------------------------------------------


JSONDict = MutableDict.as_mutable(JSON)


class Choice:
    def __init__(self, code, value):
        self.code = int(code)
        self.value = value

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.code == other.code

        return self.code == other

    def __lt__(self, other):
        if isinstance(other, self.__class__):
            return self.code < other.code

        return self.code < other

    def __le__(self, other):
        if isinstance(other, self.__class__):
            return self.code <= other.code

        return self.code <= other

    def __gt__(self, other):
        return not (self <= other)

    def __ge__(self, other):
        return not (self < other)

    def __hash__(self):
        return hash(self.code)

    def __repr__(self):
        return f"Choice(code={self.code}, value={self.value})"


class Coordinate:
    def __init__(self, x, y):
        self.x = float(x)
        self.y = float(y)

    def wk_text(self):
        return f"SRID=4326;POINT({self.x} {self.y})"

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.x == other.x and self.y == other.y

        if isinstance(other, list | tuple):
            return (self.x, self.y) == tuple(other)[:2]

        if isinstance(other, dict):
            return (self.x, self.y) == (other.get("x"), other.get("y"))

        return False

    def __hash__(self):
        return hash((self.x, self.y))

    def __repr__(self):
        return f"Coordinate(x={self.x}, y={self.y})"


class Password:
    def __init__(self, hash, context):
        self.context = context

        if isinstance(hash, bytes | None):
            self.hash = hash
        else:
            self.hash = self.context.hash(str(hash)).encode()

    def __eq__(self, other):
        if isinstance(other, bytes | None):
            return self.hash == other

        if isinstance(other, self.__class__):
            return self.hash == other.hash

        return self.context.verify(str(other), self.hash)

    def __hash__(self):
        return hash(self.hash)

    def __repr__(self):
        return str(self.hash)


class ChoiceType(TypeDecorator):
    impl = Integer()
    cache_ok = False

    def __init__(self, choices):
        self.choices = choices

    def make(self, obj):
        choice = None

        choices_dict = dict(self.choices)

        if isinstance(obj, Choice):
            if obj.code in choices_dict:
                if obj.value == choices_dict.get(obj.code):
                    choice = obj

        if isinstance(obj, dict):
            if obj.get("code") in choices_dict:
                if obj.get("value") == choices_dict.get(obj.get("code")):
                    choice = Choice(obj.get("code"), obj.get("value"))

        if isinstance(obj, int | str):
            obj = int(obj)

            if obj in choices_dict:
                choice = Choice(obj, choices_dict.get(obj))

        return choice

    def process_bind_param(self, value, dialect):
        choice = self.make(value)

        if choice is None:
            return None

        return choice.code

    def process_result_value(self, value, dialect):
        return self.make(value)

    def __repr__(self):
        return self.impl.__repr__()


class CoordinateType(Geometry):
    cache_ok = False

    def __init__(self, **kwargs):
        kwargs.update(
            geometry_type="POINT",
            srid=4326,
            spatial_index=False,
        )

        super().__init__(**kwargs)

    def make(self, obj):
        coordinate = None

        if isinstance(obj, Coordinate):
            coordinate = obj

        if isinstance(obj, list | tuple):
            coordinate = Coordinate(obj[0], obj[1])

        if isinstance(obj, dict):
            coordinate = Coordinate(obj.get("x"), obj.get("y"))

        if coordinate is None:
            from geoalchemy2.shape import to_shape

            try:
                bounds = to_shape(WKBElement(obj, extended=True)).bounds
                coordinate = Coordinate(bounds[0], bounds[1])
            except Exception:
                pass

        return coordinate

    def bind_processor(self, dialect):
        def process(value):
            coordinate = self.make(value)

            if coordinate is None:
                return None

            return coordinate.wk_text()

        return process

    def result_processor(self, dialect, coltype):
        def process(value):
            return self.make(value)

        return process


class PasswordType(TypeDecorator):
    impl = LargeBinary(512)
    cache_ok = False

    def __init__(self, **kwargs):
        self.context = LazyCryptContext(schemes=["pbkdf2_sha512"])

    def make(self, obj):
        password = None

        if isinstance(obj, Password):
            password = obj

        if isinstance(obj, str):
            password = Password(obj, self.context)

        return password

    def process_bind_param(self, value, dialect):
        password = self.make(value)

        if password is None:
            return None

        return password.hash

    def process_result_value(self, value, dialect):
        if value is None:
            return None

        return Password(value, self.context)


class StorageBaseType(TypeDecorator):
    impl = String(254)

    def __init__(self, **kwargs):
        self.prefix = ""

    def put_content(self, object_key, content, mock=False):
        return None

    def process_bind_param(self, value, dialect):
        if value and value.startswith(self.prefix):
            return value[len(self.prefix) + 1 :]

        return value

    def process_result_value(self, value, dialect):
        if value and not value.startswith(self.prefix):
            return urljoin(self.prefix, value)

        return value


class StorageAliyunType(StorageBaseType):
    def __init__(self, **kwargs):
        import oss2

        self.bucket_name = "beihe"
        self.bucket = oss2.Bucket(
            auth=oss2.Auth(
                access_key_id="LTAI4Fvw8NdKtZH3BTGJspLF",
                access_key_secret="Y8pFCzVdFYthqc7w2AK6nNv8LoSCDX",
            ),
            endpoint="https://oss-cn-shenzhen.aliyuncs.com",
            bucket_name=self.bucket_name,
        )

        self.prefix = self.bucket._make_url(self.bucket_name, "").strip("/")

    def put_content(self, object_key, content, mock=False):
        if not mock:
            try:
                r = self.bucket.put_object(object_key, content)

                if r.status < 300:
                    return r.resp.response.url
            except Exception:
                pass

            return None
        else:
            return urljoin(self.prefix, object_key)


class StorageHuaweiType(StorageBaseType):
    def __init__(self, **kwargs):
        from obs import ObsClient

        self.bucket_name = "beihe"
        self.endpoint = ObsClient(
            access_key_id="WNCYNXAUX3AAFYN8NRXG",
            secret_access_key="GT2t6mybbFmMKJMR7pB4s19Q6sok9WnOlkcpHeWY",
            server="https://obs.cn-south-1.myhuaweicloud.com",
        )

        self.prefix = self.endpoint.calling_format.get_full_url(
            self.endpoint.is_secure,
            self.endpoint.server,
            self.endpoint.port,
            self.bucket_name,
            "",
            None,
        ).strip("/")

    def put_content(self, object_key, content, mock=False):
        if not mock:
            try:
                r = self.endpoint.putContent(
                    self.bucket_name,
                    object_key,
                    content=content,
                )

                if r.status < 300:
                    return r.body.objectUrl
            except Exception:
                pass

            return None
        else:
            return urljoin(self.prefix, object_key)


class StoragePathType(StorageBaseType):
    def __init__(self, request=None):
        from config.config import BASE_DIR

        self.dirname = Path(BASE_DIR)

        if request is None:
            self.prefix = "http://127.0.0.1:8000"
        else:
            origin = (
                request.headers.get("origin") or request.headers.get("referer") or ""
            )

            if origin.startswith("http://"):
                schema = "http"
            else:
                schema = "https"

            host = request.headers.get("host")

            self.prefix = f"{schema}//{host}"

    def put_content(self, object_key, content, mock=False):
        if not mock:
            filename = self.dirname.joinpath(object_key).resolve()
            filename.parent.mkdir(parents=True, exist_ok=True)

            with open(filename.as_posix(), "rb") as f:
                f.write(content)

            return urljoin(self.prefix, object_key)
        else:
            return urljoin(self.prefix, object_key)


class StorageType(StorageAliyunType):
    pass
